package weChat.server;

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

/**
 * 聊天室服务端
 */
public class Server {
    /*
        运行在服务端的ServerSocket主要有两个作用
        1:向系统申请服务端口，客户端的Socket就是通过这个端口与服务端建立连接的。
        2:监听服务端口，一旦一个客户端通过该端口建立连接会自动创建一个Socket，服务端
          就可以通过这个Socket与客户端交互了。

        如果我们把Socket比喻为电话，那么ServerSocket相当于是某客服中心的总机。
     */
    private ServerSocket serverSocket;
    /*
        内部类可以访问外部类的成员,因此在Server类上定义一个数组allOut可以被所有
        内部类ClientHandler实例访问.从而将这些ClientHandler实例之间想互访的数据
        存放在这个数组中达到共享数据的目的
     */
//    private PrintWriter[] allOut = {};
    //ArrayList不是线程安全的。
//    private Collection<PrintWriter> allOut = new ArrayList<>();
    /*
        创建一个线程安全的集合
        线程安全的集合中，add,remove等方法上都直接使用了synchronized修饰，多个线程是不能
        同时调用一个集合的这些方法的，保证同步执行。
     */
    private List<PrintWriter> allOut = Collections.synchronizedList(new ArrayList<>());

    public Server(){
        try {
            System.out.println("正在启动服务端...");
            /*
                实例化是要指定服务端口，如果该端口被当前系统其他应用程序占用时，会
                抛出异常:java.net.BindException:address already in use
             */
            serverSocket = new ServerSocket(8088);
            System.out.println("服务端启动完毕");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public void start(){
        try {
            /*
                ServerSocket提供的方法:
                Socket accept()
                该方法是一个阻塞方法，调用后程序进入阻塞状态("卡住"),直到一个客户端
                实例化Socket与当前客户端建立连接，此时accept方法会立即返回一个Socket
                实例，服务端通过它就可以与客户端交互了。

                可以理解为这个动作相当于是总机的"接电话"操作。
             */
            while(true) {
                System.out.println("等待客户端连接...");
                Socket socket = serverSocket.accept();
                System.out.println("一个客户端连接了!");
                //启动一个线程来负责与该客户端交互
                //1创建线程任务
                ClientHandler handler = new ClientHandler(socket);
                //2创建一个线程并执行该任务
                Thread t = new Thread(handler);
                //3启动该线程
                t.start();
            }

        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        Server server = new Server();
        server.start();
    }

    /**
     * 该线程任务用于与指定的客户端进行交互。
     * 每个连接服务端的客户端都是通过该线程进行交互的。即:一个客户端靠一个该线程进行交互
     */
    private class ClientHandler implements Runnable{
        private Socket socket;
        private String host;//当前线程处理的客户端的地址信息

        public ClientHandler(Socket socket){
            this.socket = socket;
            //通过socket获取远端计算机地址信息(对于服务端而言，远端就是客户端)
            host = socket.getInetAddress().getHostAddress();
        }

        public void run(){
            PrintWriter pw = null;//放在这里为了能在finally中看到并处理
            try{
                /*
                    Socket提供的方法:
                    InputStream getInputStream()
                    通过该方法获取的字节输入流读取的是远端计算机发送过来的数据。

                    这里相当于是读取当前服务端中这个Socket对应的远端(客户端)那边Socket获取
                    的输出流写出的字节数据。
                 */
                InputStream in = socket.getInputStream();
                InputStreamReader isr = new InputStreamReader(in,"UTF-8");
                BufferedReader br = new BufferedReader(isr);

                pw = new PrintWriter(
                        new BufferedWriter(
                                new OutputStreamWriter(
                                        socket.getOutputStream(),
                                        "UTF-8"
                                )
                        ),true
                );
                /*
                    将该输出流存入共享数组allOut中,便于其他ClientHandler将消息
                    发送给当前客户端
                 */
//                synchronized (Server.this) {
                //1对allOut数组扩容
//                    allOut = Arrays.copyOf(allOut, allOut.length + 1);
                //2将输出流存入数组最后一个位置
//                    allOut[allOut.length - 1] = pw;

//                    allOut.add(pw);
//                }

                allOut.add(pw);//由于是线程安全的集合，不必自行添加锁机制即可保证同步调用

//                System.out.println(host+"上线了,当前在线人数:"+allOut.length);
//                sendMessage(host+"上线了,当前在线人数:"+allOut.length);//广播上线通知
                System.out.println(host+"上线了,当前在线人数:"+allOut.size());
                sendMessage(host+"上线了,当前在线人数:"+allOut.size());//广播上线通知

                String line;
                while((line = br.readLine()) != null) {
                    System.out.println(host+"说:"+line);
                    //将消息回复给所有客户端
                    sendMessage(host + "说:" + line);
                }
            }catch(IOException e){
                e.printStackTrace();
            }finally{
                //处理客户端断开连接后的操作

                //将当前客户端的输出流从allOut数组中删除掉
//                synchronized (Server.this){
//                    for(int i=0;i<allOut.length;i++){
//                        if(allOut[i]==pw){
//                            allOut[i] = allOut[allOut.length-1];
//                            allOut = Arrays.copyOf(allOut,allOut.length-1);
//                            break;
//                        }
//                    }
//                    allOut.remove(pw);
//                }

                allOut.remove(pw);

//                sendMessage(host+"下线了,当前在线人数:"+allOut.length);
                sendMessage(host+"下线了,当前在线人数:"+allOut.size());
                try {
                    //关闭socket释放资源
                    socket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }//run()方法结束
        /**
         * 将消息转发给所有客户端
         * @param message
         */
        private void sendMessage(String message){
//            synchronized (Server.this) {
//                for (PrintWriter pw : allOut) {
//                    pw.println(message);
//                }
//            }

            allOut.forEach(pw->pw.println(message));
        }
    }
}
